﻿/*
 * CLiveDevice.cpp
 *
 *  Created on: 2016年4月28日
 *      Author: terry
 */

#include "CLiveDevice.h"
#include "AppProperties.h"

#include "CLog.h"
#include "MediaControlStanza.h"
#include "TStringUtil.h"
#include "Socket.h"
#include "MediaCodecName.h"
#include <errno.h>
#include "XmppUtil.h"


namespace av
{

    static const int KEEPALIVE_IDLE = 1;
    static const int KEEPALIVE_INTERVAL = 1;
    static const int KEEPALIVE_COUNT = 2;


CLiveDevice::CLiveDevice():
    m_channels(),
    m_event()
{
	m_localIp = comn::SockAddr::getHostIP();
}

CLiveDevice::~CLiveDevice()
{
	close();
}

void CLiveDevice::Startup()
{
	AppProperties::setPath("LiveDevice.ini");

    CLog::setLogger(CLog::COUT, CLog::kNone, CLog::TIME);
	CLog::setLogger(CLog::DEBUGWINDOW, CLog::kNone, CLog::TIME);

	CLog::setLogger(CLog::FILE);
	CLog::setFileParam("LiveDevice.log", 2, 2);

#ifdef WIN32
#else
	CLog::setLogger(CLog::COUT);
#endif //


	rtpcaster_init();
}

void CLiveDevice::Cleanup()
{
	rtpcaster_quit();
}

void CLiveDevice::setLocalIp(const char* ip)
{
	if (!ip)
	{
		return;
	}

	m_localIp = ip;
}

void CLiveDevice::setServer(const char* ip, const char* username, const char* password)
{
	if (!ip || !username || !password)
	{
		return;
	}

	m_server = ip;
	m_username = username;
	m_password = password;
}

void CLiveDevice::setVideoFormat(int chl, int width, int height, int fps, int codec)
{
    if (chl >= (int)m_channels.size())
    {
        return ;
    }

    MediaChannelPtr& channel = m_channels[chl];

    channel->setVideoFormat(width, height, fps, codec);
}

void CLiveDevice::setAudioFormat(int chl, int channels, int sampleRate, int codec)
{
    if (chl >= (int)m_channels.size())
    {
        return ;
    }

    MediaChannelPtr& channel = m_channels[chl];
    channel->setAudioFormat(channels, sampleRate, codec);
}

int CLiveDevice::open(int chl)
{
    if (chl <= 0)
	{
        return EINVAL;
	}

    int videoPort = 2000;
    int audioPort = 2002;
    for (int i = 0; i < chl; i ++)
    {
        MediaChannelPtr channel(new MediaChannel());
        channel->setPort(videoPort, audioPort);
        m_channels.push_back(channel);

        videoPort += 8;
        audioPort += 8;
    }

	openClient();

	start();

	return 0;
}

void CLiveDevice::close()
{
	if (isRunning())
	{
		stop();
	}

	for (size_t i = 0; i < m_channels.size(); i ++)
	{
        m_channels[i]->close();
	}

	closeClient();
}

bool CLiveDevice::isOpen()
{
	return (m_channels.size() > 0) && (m_client);
}

void CLiveDevice::inputVideo(int chl, uint8_t* data, int size, int64_t pts)
{
    if (chl >= (int)m_channels.size())
    {
        return ;
    }

    MediaChannelPtr& channel = m_channels[chl];
    channel->inputVideo(data, size, pts);
    
}

void CLiveDevice::inputAudio(int chl, uint8_t* data, int size, int64_t pts)
{
    if (chl >= (int)m_channels.size())
    {
        return ;
    }

    MediaChannelPtr& channel = m_channels[chl];
    channel->inputAudio(data, size, pts);

}

int CLiveDevice::run()
{
	while (!m_canExit)
	{
		int timeout = 1000 * 1000;
		ConnectionError ce = m_client->recv(timeout);
		if (ce == ConnNoError)
		{
			/// pass
		}
		else
		{
            m_event.timedwait(1000 * 10);

			m_client->connect(false);
            
            comn::IniProperties& props = AppProperties::getProperties();
            int idle = props.getInt("KeepAlive.Idle", KEEPALIVE_IDLE);
            int interval = props.getInt("KeepAlive.Interval", KEEPALIVE_INTERVAL);
            int count = props.getInt("KeepAlive.Count", KEEPALIVE_COUNT);
            m_client->setKeepAlive(true, idle, interval, count);

			CLog::info("reconnect.\n");
		}
	}
	return 0;
}

void CLiveDevice::doStop()
{
	m_event.post();
}

bool CLiveDevice::startup()
{
	return true;
}

void CLiveDevice::cleanup()
{
	// pass
}

bool CLiveDevice::handleIq( const IQ& iq )
{
	CLog::debug("CLiveDevice::handleIq. subtype:%d %s, jid:%s\n", iq.subtype(), toString(iq.subtype()),
		iq.from().full().c_str());

	const MediaControlStanza* stanza = iq.findExtension<MediaControlStanza>(MediaControlStanza::EXTENSION_TYPE);
	if (!stanza)
	{
		CLog::error("CLiveDevice::handleIq. it's not MediaControlStanza.\n");
		return false;
	}

	std::string cmd = stanza->getCommand();

    std::string chlName = stanza->getChannel();
    int chl = 0;
    comn::StringCast::toValue(chlName, chl);

	CLog::debug("CLiveDevice::handleIq. cmd:%s, %s\n", cmd.c_str(), chlName.c_str());

    /// check channel range
    if (chl < 0 || chl >= (int)m_channels.size())
    {
		CLog::error("CLiveDevice::handleIq. chl is invalid:%d. max:%d\n", chl, m_channels.size());

        return false;
    }

    MediaChannelPtr& channel = m_channels[chl];

	if (cmd == "desc")
	{
		IQ result(IQ::Result, iq.from(), iq.id());
		MediaControlStanza* replyStanza = new MediaControlStanza(NULL);
		replyStanza->setCommand(cmd);
        replyStanza->setChannel(chlName);

		std::string audioUrl;
		std::string videoUrl = makeUrl(channel->getFormat(), audioUrl);
		replyStanza->setUrl(videoUrl, audioUrl);

		result.addExtension(replyStanza);
        m_client->send(result);

		CLog::debug("video url: %s\n", videoUrl.c_str());
		CLog::debug("audioUrl url: %s\n", audioUrl.c_str());

		return true;
	}
	else if (cmd == "setup")
	{
		std::string audioUrl;
		std::string videoUrl;
		stanza->getUrl(videoUrl, audioUrl);

		RtpMediaFormat videoRtpFormat;
		videoRtpFormat.parse(videoUrl);
		int protocol = getProtocol(videoRtpFormat);
        channel->addTarget(MEDIA_TYPE_VIDEO, protocol, videoRtpFormat.m_ip.c_str(), videoRtpFormat.m_port);

		RtpMediaFormat audioRtpFormat;
		audioRtpFormat.parse(audioUrl);
        channel->addTarget(MEDIA_TYPE_AUDIO, protocol, audioRtpFormat.m_ip.c_str(), audioRtpFormat.m_port);


		IQ result(IQ::Result, iq.from(), iq.id());
		MediaControlStanza* replyStanza = new MediaControlStanza(NULL);
		replyStanza->setCommand(cmd);
        replyStanza->setChannel(chlName);

		videoUrl = makeMediaUrl(channel->getFormat(), channel->getVideoPort(), channel->getAudioPort(), audioUrl);
		replyStanza->setUrl(videoUrl, audioUrl);

		result.addExtension(replyStanza);
		m_client->send(result);

		return true;
	}
	else if (cmd == "play")
	{
	}
	else if (cmd == "teardown")
	{
        channel->removeTarget(MEDIA_TYPE_VIDEO);
        channel->removeTarget(MEDIA_TYPE_AUDIO);

		IQ result(IQ::Result, iq.from(), iq.id());
		MediaControlStanza* replyStanza = new MediaControlStanza(NULL);
		replyStanza->setCommand(cmd);
        replyStanza->setChannel(chlName);

		result.addExtension(replyStanza);
		m_client->send(result);

		return true;
	}

	return false;
}

void CLiveDevice::handleIqID( const IQ& iq, int context )
{
    // pass
}

void CLiveDevice::onConnect()
{
	std::string jid = getJid();
	CLog::info("connected to server. %s\n", jid.c_str());
}

void CLiveDevice::onDisconnect( ConnectionError e )
{
	CLog::warning("disconnected from server.\n");
}

bool CLiveDevice::onTLSConnect( const CertInfo& info )
{
	CLog::info("CLiveDevice::onTLSConnect.\n");

	return true;
}

std::string CLiveDevice::getJid() const
{
	comn::IniProperties& props = AppProperties::getProperties();

	std::string server = props.getString("Xmpp.Server", m_server);
	std::string user = props.getString("Xmpp.User", m_username);
	std::string jid = user + "@" + server + "/Device";
	return jid;
}

void CLiveDevice::openClient()
{
	comn::IniProperties& props = AppProperties::getProperties();
	std::string password = props.getString("Xmpp.Password", m_password);
	std::string jid = getJid();

	CLog::info("connect to xmpp: %s\n", jid.c_str());

	m_client.reset(new Client(jid, password));

	m_client->registerStanzaExtension(new MediaControlStanza(0));

	m_client->registerConnectionListener(this);
	m_client->registerIqHandler(this, MediaControlStanza::EXTENSION_TYPE);
	m_client->connect(false);

    int idle = props.getInt("KeepAlive.Idle", KEEPALIVE_IDLE);
    int interval = props.getInt("KeepAlive.Interval", KEEPALIVE_INTERVAL);
    int count = props.getInt("KeepAlive.Count", KEEPALIVE_COUNT);
    m_client->setKeepAlive(true, idle, interval, count);
}

void CLiveDevice::closeClient()
{
	m_client.reset();
}

std::string CLiveDevice::makeUrl(const MediaFormat& fmt, std::string& audioUrl)
{
	RtpMediaFormat audioRtpFmt;
	audioRtpFmt.m_codec = MediaCodecName::getName(fmt.m_audioCodec);
	audioRtpFmt.m_payload = RtpMediaFormat::makePayload(audioRtpFmt.m_codec);
	audioRtpFmt.m_clockRate = fmt.m_sampleRate;
	audioRtpFmt.m_channels = fmt.m_channels;
	audioRtpFmt.m_name = RtpMediaFormat::AUDIO;

	audioUrl = audioRtpFmt.toUrl();

	RtpMediaFormat rtpFmt;
	rtpFmt.m_codec = MediaCodecName::getName(fmt.m_codec);
	rtpFmt.m_payload = RtpMediaFormat::makePayload(rtpFmt.m_codec);
	rtpFmt.m_clockRate = 90000;
	rtpFmt.m_fmtp = comn::StringUtil::format("width=%d&height=%d", fmt.m_width, fmt.m_height);
	rtpFmt.m_name = RtpMediaFormat::VIDEO;

	return rtpFmt.toUrl();
}

std::string CLiveDevice::makeMediaUrl(const MediaFormat& fmt, int videoPort, int audioPort, std::string& audioUrl)
{
	RtpMediaFormat audioRtpFmt;
	audioRtpFmt.m_codec = MediaCodecName::getName(fmt.m_audioCodec);
	audioRtpFmt.m_payload = RtpMediaFormat::makePayload(audioRtpFmt.m_codec);
	audioRtpFmt.m_clockRate = fmt.m_sampleRate;
	audioRtpFmt.m_channels = fmt.m_channels;
	audioRtpFmt.m_name = RtpMediaFormat::AUDIO;

	audioRtpFmt.m_port = audioPort;
	audioRtpFmt.m_ip = getHostIP();

	audioUrl = audioRtpFmt.toUrl();


	RtpMediaFormat rtpFmt;
	rtpFmt.m_name = RtpMediaFormat::VIDEO;
	rtpFmt.m_codec = MediaCodecName::getName(fmt.m_codec);
	rtpFmt.m_payload = 96;
	rtpFmt.m_clockRate = 90000;
	rtpFmt.m_fmtp = comn::StringUtil::format("width=%d&height=%d", fmt.m_width, fmt.m_height);

	rtpFmt.m_port = videoPort;
	rtpFmt.m_ip = getHostIP();

	return rtpFmt.toUrl();
}

std::string CLiveDevice::getHostIP()
{
	comn::IniProperties& props = AppProperties::getProperties();
	std::string ip = props.getString("App.Host", m_localIp);
	if (ip.empty())
	{
		ip = comn::SockAddr::getHostIP();
	}
	return ip;
}

int CLiveDevice::getProtocol(const RtpMediaFormat& fmt)
{
	int protocol = kProtocolUdp;
	if (fmt.m_protocol == RtpMediaFormat::RTP_OVER_TCP)
	{
		protocol = kProtocolTcp;
	}
	return protocol;
}


} /* namespace av */
